🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / ota / WifiOtaTransport.kt
Displaying Raw • Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/WifiOtaTransport.kt renovate/actions-setup-java-5.x (f17e586c) Text, 8.34 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware.ota
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787io.ktor.network.selector.SelectorManager
Tff7b72import T7ee787io.ktor.network.sockets.InetSocketAddress
Tff7b72import T7ee787io.ktor.network.sockets.Socket
Tff7b72import T7ee787io.ktor.network.sockets.aSocket
Tff7b72import T7ee787io.ktor.network.sockets.openReadChannel
Tff7b72import T7ee787io.ktor.network.sockets.openWriteChannel
Tff7b72import T7ee787io.ktor.utils.io.ByteReadChannel
Tff7b72import T7ee787io.ktor.utils.io.ByteWriteChannel
Tff7b72import T7ee787io.ktor.utils.io.readLine
Tff7b72import T7ee787io.ktor.utils.io.writeFully
Tff7b72import T7ee787io.ktor.utils.io.writeStringUtf8
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.withContext
Tff7b72import T7ee787kotlinx.coroutines.withTimeout
Tff7b72import T7ee787org.meshtastic.core.common.util.ioDispatcher
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatching
T8b949e/**
* WiFi/TCP transport implementation for ESP32 Unified OTA protocol.
*
* Uses Ktor raw sockets for KMP-compatible TCP communication. UDP discovery is not included in this common
* implementation and should be handled by platform-specific code.
*
* Unlike BLE, WiFi transport:
* - Uses synchronous TCP (no manual ACK waiting)
* - Supports larger chunk sizes (up to 1024 bytes)
* - Generally faster transfer speeds
*/
Tff7b72class T56d364WifiOtaTransportTb4b4b4(Tff7b72private Tff7b72val Te6edf3deviceIpAddressTb4b4b4: Tffa657StringTb4b4b4, Tff7b72private Tff7b72val Te6edf3portTb4b4b4: Tffa657Int Tff7b72= Te6edf3DEFAULT_PORTTb4b4b4) Tb4b4b4: Te6edf3UnifiedOtaProtocol Tb4b4b4{
Tff7b72private Tff7b72var Te6edf3selectorManagerTb4b4b4: Te6edf3SelectorManager? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3socketTb4b4b4: Te6edf3Socket? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3writeChannelTb4b4b4: Te6edf3ByteWriteChannel? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3readChannelTb4b4b4: Te6edf3ByteReadChannel? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3isConnected Tff7b72= Tff7b72false
T8b949e/** Connect to the device via TCP using Ktor raw sockets. */
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Connecting to Tffd700$Te6edf3deviceIpAddressTa5d6ff:Tffd700$Te6edf3portTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3selector Tff7b72= Te6edf3SelectorManagerTb4b4b4(Te6edf3ioDispatcherTb4b4b4)
Te6edf3selectorManager Tff7b72= Te6edf3selector
Tff7b72val Te6edf3tcpSocket Tff7b72=
Te6edf3withTimeoutTb4b4b4(Te6edf3CONNECTION_TIMEOUT_MSTb4b4b4) Tb4b4b4{
Te6edf3aSocketTb4b4b4(Te6edf3selectorTb4b4b4)Tb4b4b4.Te6edf3tcpTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3connectTb4b4b4(Te6edf3InetSocketAddressTb4b4b4(Te6edf3deviceIpAddressTb4b4b4, Te6edf3portTb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3socket Tff7b72= Te6edf3tcpSocket
Te6edf3writeChannel Tff7b72= Te6edf3tcpSocketTb4b4b4.Te6edf3openWriteChannelTb4b4b4(Te6edf3autoFlush Tff7b72= Tff7b72falseTb4b4b4)
Te6edf3readChannel Tff7b72= Te6edf3tcpSocketTb4b4b4.Te6edf3openReadChannelTb4b4b4(Tb4b4b4)
Te6edf3isConnected Tff7b72= Tff7b72true
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Connected successfullyTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Connection failedTa5d6ff" Tb4b4b4}
Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffstartOtaTb4b4b4(
Te6edf3sizeBytesTb4b4b4: Tffa657LongTb4b4b4,
Te6edf3sha256HashTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3onHandshakeStatusTb4b4b4: Te6edf3suspend Tb4b4b4(Te6edf3OtaHandshakeStatusTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3safeCatching Tb4b4b4{
Tff7b72val Te6edf3command Tff7b72= Te6edf3OtaCommandTb4b4b4.Te6edf3StartOtaTb4b4b4(Te6edf3sizeBytesTb4b4b4, Te6edf3sha256HashTb4b4b4)
Te6edf3sendCommandTb4b4b4(Te6edf3commandTb4b4b4)
Tff7b72var Te6edf3handshakeComplete Tff7b72= Tff7b72false
Tff7b72while Tb4b4b4(Tff7b72!Te6edf3handshakeCompleteTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3readResponseTb4b4b4(Te6edf3ERASING_TIMEOUT_MSTb4b4b4)
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3parsed Tff7b72= Te6edf3OtaResponseTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3responseTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Ok Tff7b72-Tff7b72> Te6edf3handshakeComplete Tff7b72= Tff7b72true
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Erasing Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Device erasing flash...Ta5d6ff" Tb4b4b4}
Te6edf3onHandshakeStatusTb4b4b4(Te6edf3OtaHandshakeStatusTb4b4b4.Te6edf3ErasingTb4b4b4)
Tb4b4b4}
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Error Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3parsedTb4b4b4.Te6edf3messageTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffHash RejectedTa5d6ff"Tb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3HashRejectedTb4b4b4(Te6edf3sha256HashTb4b4b4)
Tb4b4b4}
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3CommandFailedTb4b4b4(Te6edf3commandTb4b4b4, Te6edf3parsedTb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Unexpected handshake response: Tffd700$Te6edf3responseTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexityTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffstreamFirmwareTb4b4b4(
Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3chunkSizeTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3isConnectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffNot connectedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3wc Tff7b72= Te6edf3writeChannel Tff7b72?: Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffNot connectedTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3totalBytes Tff7b72= Te6edf3dataTb4b4b4.Te6edf3size
Tff7b72var Te6edf3sentBytes Tff7b72= T79c0ff0
Tff7b72while Tb4b4b4(Te6edf3sentBytes Tff7b72< Te6edf3totalBytesTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3remainingBytes Tff7b72= Te6edf3totalBytes Tff7b72- Te6edf3sentBytes
Tff7b72val Te6edf3currentChunkSize Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3chunkSizeTb4b4b4, Te6edf3remainingBytesTb4b4b4)
T8b949e// Write chunk directly to TCP stream — no per-chunk ACK needed over TCP.
T8b949e// Ktor writeFully uses (startIndex, endIndex), NOT (offset, length).
Te6edf3wcTb4b4b4.Te6edf3writeFullyTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3sentBytesTb4b4b4, Te6edf3sentBytes Tff7b72+ Te6edf3currentChunkSizeTb4b4b4)
Te6edf3wcTb4b4b4.Te6edf3flushTb4b4b4(Tb4b4b4)
Te6edf3sentBytes Tff7b72+Tff7b72= Te6edf3currentChunkSize
Te6edf3onProgressTb4b4b4(Te6edf3sentBytesTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3totalBytesTb4b4b4)
T8b949e// Small delay to avoid overwhelming the device
Te6edf3delayTb4b4b4(Te6edf3WRITE_DELAY_MSTb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Firmware streaming complete (Tffd700$Te6edf3sentBytesTa5d6ff bytes)Ta5d6ff" Tb4b4b4}
T8b949e// Wait for final verification response (loop until OK or Error)
Tff7b72var Te6edf3finalHandshakeComplete Tff7b72= Tff7b72false
Tff7b72while Tb4b4b4(Tff7b72!Te6edf3finalHandshakeCompleteTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3finalResponse Tff7b72= Te6edf3readResponseTb4b4b4(Te6edf3VERIFICATION_TIMEOUT_MSTb4b4b4)
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3parsed Tff7b72= Te6edf3OtaResponseTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3finalResponseTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Ok Tff7b72-Tff7b72> Te6edf3finalHandshakeComplete Tff7b72= Tff7b72true
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Ack Tff7b72-Tff7b72> Tb4b4b4{Tb4b4b4}
T8b949e// Ignore late ACKs
Tff7b72is Te6edf3OtaResponseTb4b4b4.Te6edf3Error Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3parsedTb4b4b4.Te6edf3messageTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffHash MismatchTa5d6ff"Tb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3VerificationFailedTb4b4b4(Ta5d6ff"Ta5d6ffFirmware hash mismatch after transferTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffVerification failed: Tffd700${Te6edf3parsedTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72>
Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffExpected OK after transfer, got: Tffd700$Te6edf3finalResponseTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{
Te6edf3socketTff7b72?.Te6edf3closeTb4b4b4(Tb4b4b4)
Te6edf3selectorManagerTff7b72?.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3writeChannel Tff7b72= Tff7b72null
Te6edf3readChannel Tff7b72= Tff7b72null
Te6edf3socket Tff7b72= Tff7b72null
Te6edf3selectorManager Tff7b72= Tff7b72null
Te6edf3isConnected Tff7b72= Tff7b72false
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendCommandTb4b4b4(Te6edf3commandTb4b4b4: Te6edf3OtaCommandTb4b4b4) Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3ioDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3wc Tff7b72= Te6edf3writeChannel Tff7b72?: Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffNot connectedTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3commandStr Tff7b72= Te6edf3commandTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Sending command: Tffd700${Te6edf3commandStrTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4}
Te6edf3wcTb4b4b4.Te6edf3writeStringUtf8Tb4b4b4(Te6edf3commandStrTb4b4b4)
Te6edf3wcTb4b4b4.Te6edf3flushTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffreadResponseTb4b4b4(Te6edf3timeoutMsTb4b4b4: Tffa657Long Tff7b72= Te6edf3COMMAND_TIMEOUT_MSTb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Te6edf3withTimeoutTb4b4b4(Te6edf3timeoutMsTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3rc Tff7b72= Te6edf3readChannel Tff7b72?: Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffNot connectedTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3response Tff7b72= Te6edf3rcTb4b4b4.Te6edf3readLineTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72throw Te6edf3OtaProtocolExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffConnection closedTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffWiFi OTA: Received response: Tffd700$Te6edf3responseTa5d6ff" Tb4b4b4}
Te6edf3response
Tb4b4b4}
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3DEFAULT_PORT Tff7b72= T79c0ff3T79c0ff2T79c0ff3T79c0ff2
Tff7b72const Tff7b72val Te6edf3RECOMMENDED_CHUNK_SIZE Tff7b72= T79c0ff1T79c0ff0T79c0ff2T79c0ff4 T8b949e// Larger than BLE
T8b949e// Timeouts
Tff7b72private Tff7b72const Tff7b72val Te6edf3CONNECTION_TIMEOUT_MS Tff7b72= T79c0ff5Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3COMMAND_TIMEOUT_MS Tff7b72= T79c0ff1T79c0ff0Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3ERASING_TIMEOUT_MS Tff7b72= T79c0ff6T79c0ff0Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3VERIFICATION_TIMEOUT_MS Tff7b72= T79c0ff1T79c0ff0Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3WRITE_DELAY_MS Tff7b72= T79c0ff1T79c0ff0L T8b949e// Shorter than BLE
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.07s